﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using TrainGame.Model;
using TrainGame.Engine;

namespace TrainGame.AI.TileLay
{
	/// <summary>
	/// Build towards desirable city hexes
	/// </summary>
	internal class HeadForCityTileGoal : TileLayGoal
	{
		public HeadForCityTileGoal(List<string> hexes)
		{
			_HexesAndNeeds = new List<HexCityNeed>();
			foreach (string hex in hexes)
				_HexesAndNeeds.Add(new HexCityNeed (hex, null, 0));	// city names wil be filled in on first ComputeNeed()
		}
		private List<HexCityNeed> _HexesAndNeeds;
		private class HexCityNeed
		{
			public string Hex;
			public string City;
			public int Need;
			public List<string> ConnectedHexes;

			public HexCityNeed(string hex, string city, int need)
			{
				Hex = hex;
				City = city;
				Need = need;
			}
		};

		public override string GetName(PublicCompany company)
		{
			HexCityNeed hcn = GetHighestCity();
			return "HeadForCity " + (hcn != null ? hcn.City : "<none>");
		}
		private HexCityNeed GetHighestCity()
		{
			int need = _HexesAndNeeds[0].Need;
			HexCityNeed city = _HexesAndNeeds[0];
			for (int i=1; i < _HexesAndNeeds.Count; i++)
			{
				HexCityNeed hcn = _HexesAndNeeds[i];
				if (hcn.Need > need)
				{
					need = hcn.Need;
					city = hcn;
				}
			}
			return city;
		}

		/// <summary>
		/// how important is heading for these cities?
		/// </summary>
		protected override int ComputeNeed(Gameboard gameboard, PublicCompany company, Dictionary<string, GameboardHex> runToHexes, AiMoveConfig config)
		{
			FillInCityNames(gameboard);
			ComputeCityNeeds(gameboard, company, runToHexes, config);
			HexCityNeed hcn = GetHighestCity();
			return (hcn != null) ? hcn.Need : 0;
		}

		private void FillInCityNames(Gameboard gameboard)
		{
			foreach (HexCityNeed hcn in _HexesAndNeeds)
			{
				if (hcn.City == null)
				{
					GameboardHex hex = gameboard.GameboardHexes[hcn.Hex];
					if ((hex != null) && (! string.IsNullOrEmpty (hex.City)))
						hcn.City = hex.City;
				}
			}
		}

		private void ComputeCityNeeds(Gameboard gameboard, PublicCompany company, Dictionary<string, GameboardHex> runToHexes, AiMoveConfig config)
		{
			List<string> companyHexes = new List<string>();
			foreach (GameboardHex hex in company.TokenHexes)
				GameEngine.GetConnectedHexes(gameboard, hex.Location, company, companyHexes);

			foreach (HexCityNeed hcn in _HexesAndNeeds)
			{
				if (runToHexes.ContainsKey(hcn.Hex))
				{
					// TODO: while we may reach the hex, we may not yet have an actual connection, so verify a route exists from one of our tokens to the actual target city.
					bool reachedIt = true;

					if (reachedIt)
					{
						hcn.Need = 0;	// already reached it
						continue;		// go on to next one
					}
				}

				// take into account the city's value and our distance from it (or really its span of trackage)
				GameboardHex hex = gameboard.GameboardHexes[hcn.Hex];
				int cityValue = AiUtils.GetMaxCityValue(hex.Cities[0], company.Game.Tiles, config);

				hcn.ConnectedHexes = GameEngine.GetConnectedHexes(gameboard, hcn.Hex, null);
				int distanceValue = GameEngine.CalculateHexDistance(companyHexes, hcn.ConnectedHexes);

				cityValue /= 20;
				distanceValue = Math.Max(0, (19 - distanceValue) / 6);	// 3 or less
				hcn.Need = cityValue + distanceValue;
			}
		}

		/// <summary>
		/// pick a tile lay that best heads us toward the city
		/// </summary>
		internal override List<AiTileLay> ComputeTileLays(Stopwatch watch, TimeSpan timeLimit, Gameboard gameboard,
			PublicCompany company, List<AiTileLay> tileLays,
			Dictionary<string, GameboardHex> runToHexes, Dictionary<string, GameboardHex> tileLayHexes, AiMoveConfig config)
		{
			List<AiTileLay> bestTileLays = null;
			int bestValue = -1;

			foreach (AiTileLay tileLay in tileLays)
			{
				if (watch.Elapsed.CompareTo(timeLimit) > 0)
					goto done;	// time limit expired, return the best tile lay so far

				gameboard.ApplyTileLayWithUndo(tileLay);	// add tile lay to game board, computes hex.NewRoutes
				tileLay.TransferHexNewRoutes(company, gameboard);

				ComputeTileGoalValue(tileLay, gameboard, company, runToHexes, GetHighestCity().ConnectedHexes);

				// HACK #1: less interested in running through towns for this goal
				if ((tileLay.Tile.TownType == GameboardHex.eTownType.Town) ||
				  (tileLay.Tile.TownType == GameboardHex.eTownType.DblTown))
					tileLay.GoalValue--;

				// HACK #2: double-town tile lays with no upgrade/downgrade path are worse than all others
				if ((tileLay.Tile.TownType == GameboardHex.eTownType.DblTown) &&
				  (tileLay.Tile.ValidUpgrades.Count == 0) && (tileLay.Tile.ValidDowngrades.Count == 0))
					tileLay.GoalValue--;

				gameboard.UndoTileLay(tileLay);	// restore the game board

				if (tileLay.GoalValue > 0)
				{
					if (tileLay.GoalValue == bestValue)
						bestTileLays.Add(tileLay);
					else if (tileLay.GoalValue > bestValue)
					{
						bestTileLays = new List<AiTileLay>();
						bestTileLays.Add(tileLay);
						bestValue = tileLay.GoalValue;
					}
				}
			}
		done:
			return bestTileLays;
		}

		static private void ComputeTileGoalValue(AiTileLay tileLay, Gameboard gameboard, PublicCompany company,
			Dictionary<string, GameboardHex> runToHexes, List<string> targetHexes)
		{
			tileLay.GoalValue = 0;
			if ((targetHexes == null) || (targetHexes.Count == 0))
				return;

			if (targetHexes.Contains(tileLay.Hex.Location))
			{
				// It hits a target hex
				tileLay.GoalValue = 50;	// this is it!
				return;
			}

			// look at each new route out of this hex
			foreach (string direction in tileLay.NewRoutes)
			{
				// TODO: only check routes we can run out of!  Applies to double towns and brown tiles

				// TODO: only check target hexsides we can actually run into

				int goal = 0;

				// get next hex
				string nextLoc = (string) tileLay.Hex.Neighbors[direction];
				if (targetHexes.Contains(nextLoc))
					goal = 20;	// there it is!
				// only look at next hexes we don't currently reach
				// (if we can reach it, some other tile lay will definitely have higher value)
				else if (!runToHexes.ContainsKey(nextLoc))
				{
					GameboardHex nextHex = gameboard.GameboardHexes[nextLoc];
					if (nextHex != null)
					{
						goal = 2 * (10 - GameEngine.CalculateHexDistance(nextLoc, targetHexes));
						if ((nextHex.Tile == null) && (nextHex.InitialTile.Length == 0) &&	// no initial tile either
						  nextHex.Cost == 0)	// but don't add +1 if the tile costs money
							goal += 1;	// if no tile already there, +1 (easier to build through?)
					}

					// TODO: if tile connects to additional trackage that gets us closer, need to value that higher -
					// maybe as close as we can get on that trackage (where we can lay tile next turn)
				}

				if (goal > tileLay.GoalValue)
					tileLay.GoalValue = goal;
			}
		}
	}
}
